/* 
    sample code for MPEG4 for 7113 Composite input and MPEG4 output
    This sample code is to do encode 100 stream files named "/tmp/dev0.m4v"
 */
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/timeb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <linux/videodev.h>
#include <errno.h>
#include <linux/fb.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/un.h>
#include <signal.h>

#include "fmpeg4_avcodec.h"
#include "ratecontrol.h"

#define RATE_CTL

#define FMPEG4_ENCODER_DEV  "/dev/fenc"
#define devicename "/dev/video0"
#define dout_name "/tmp/dev0.m4v"

typedef struct AVFrame {
    uint8_t *data[4];
} AVFrame;


typedef struct video_profile
{
    unsigned int bit_rate;
    unsigned int width;   //length per dma buffer
    unsigned int height;
    unsigned int framerate;
    unsigned int frame_rate_base;
    unsigned int gop_size;
    unsigned int qmax;
    unsigned int qmin;   
    unsigned int quant;
    unsigned int intra;
    AVFrame *coded_frame;
    char *priv;
} video_profile;


FILE        *dout, *din;
int             fmpeg4_enc_fd = 0;
RateControl    ratec;
video_profile   video_setting;
unsigned char           *video_buf_virt;
unsigned char           *out_virt_buffer;
unsigned int           mmap_addr;

#define TEST_WIDTH 704
#define TEST_HEIGHT 480
#define TEST_ROUND 100


typedef unsigned long long uint64;

struct timeval t1;
uint64  nsecs;
void time_start(struct timeval *tv)
{
    gettimeofday (tv, NULL);
}

uint64 time_delta(struct timeval *start, struct timeval *stop)
{
    uint64 secs, usecs;
    
    secs = stop->tv_sec - start->tv_sec;
    usecs = stop->tv_usec - start->tv_usec;
    if (usecs < 0)
    {
        secs--;
        usecs += 1000000;
    }
    return secs * 1000000 + usecs;
}

uint64 time_stop(struct timeval *tv)
{
    struct timeval end;
    gettimeofday (&end, NULL);
    return time_delta (tv, &end);
}


int mpeg4_init(video_profile *video_setting)
{
    FMP4_ENC_PARAM  enc_param;
   
    fmpeg4_enc_fd = open( FMPEG4_ENCODER_DEV,O_RDWR );
    if(fmpeg4_enc_fd == 0)   {
	printf("Fail to open %s\n",FMPEG4_ENCODER_DEV);
	return -1;
    }    

    enc_param.u32BitRate=video_setting->bit_rate;
    enc_param.u32FrameWidth=video_setting->width;
    enc_param.u32FrameHeight=video_setting->height;
    enc_param.fFrameRate=(float)video_setting->framerate/(float)video_setting->frame_rate_base;
    enc_param.u32InitialQuant=video_setting->quant;
    enc_param.u32IPInterval=video_setting->gop_size;
    enc_param.bEnable4MV=0;
    enc_param.bH263Quant=1;
    
#if 0 //def H263	
    enc_param.bShortHeader=1;
#else
    enc_param.bShortHeader=0;
#endif
    enc_param.bResyncMarker=0;
    enc_param.u32MaxQuant=video_setting->qmax;
    enc_param.u32MinQuant=video_setting->qmin;    
	
    enc_param.bROIEnable   = 0;     //0:disable 1:enable
    enc_param.u32ROIX      = 0;     // must be a multiple of 16 pixels
    enc_param.u32ROIY      = 0;     // must be a multiple of 16 pixels
    enc_param.u32ROIWidth  = enc_param.u32FrameWidth;   // must be a multiple of 16 pixels
    enc_param.u32ROIHeight = enc_param.u32FrameHeight;  // must be a multiple of 16 pixels
    //enc_param.motion_dection_enable=0;

    RateControlInit(&ratec,enc_param.u32BitRate*1000, DELAY_FACTOR, RC_AVERAGE_PERIOD,
                    ARG_RC_BUFFER, get_framerate(enc_param.fFrameRate),
                    enc_param.u32MaxQuant,enc_param.u32MinQuant,enc_param.u32InitialQuant);

    if (  ioctl(fmpeg4_enc_fd,FMPEG4_IOCTL_ENCODE_INIT,&enc_param) < 0)  {
    	    close(fmpeg4_enc_fd);
	    printf("Error to set FMPEG4_IOCTL_INIT\n");
	    return -1;
    }
    
	return 0;
}



int enc_close(video_profile *video_setting)
{
	if(fmpeg4_enc_fd)
		close(fmpeg4_enc_fd);
	fmpeg4_enc_fd = 0;
	free(video_setting->coded_frame);
	return 0;
}

int mpeg4_encode(video_profile *video_setting, unsigned char *buf,void * data)
{
    AVFrame             *pict=(AVFrame *)data;
    Faraday_ENC_FRAME   enc_frame;

    enc_frame.pu8YFrameBaseAddr = (unsigned char *)pict->data[0];   //input user continued virtual address (Y), Y=0 when NVOP
    enc_frame.pu8UFrameBaseAddr = (unsigned char *)pict->data[1];   //input user continued virtual address (U)
    enc_frame.pu8VFrameBaseAddr = (unsigned char *)pict->data[2];   //input user continued virtual address (V)
    enc_frame.bitstream = buf;  //output User Virtual address   
    enc_frame.quant = ratec.rtn_quant;

    enc_frame.intra = -1;
    if (  ioctl(fmpeg4_enc_fd, FMPEG4_IOCTL_ENCODE_FRAME, &enc_frame) <0)     {
	    printf("Error to set FMPEG4_IOCTL_ENCODE_FRAME\n");
	    return -1;
    }

#ifdef RATE_CTL
    RateControlUpdate(&ratec, enc_frame.quant, enc_frame.length, enc_frame.intra);
#endif

    video_setting->intra = enc_frame.intra; //the return value in enc_frame.intra

    return enc_frame.length;
}

int main(int argc, char **argv)
{
    int length,fcount=0,tlength;
    AVFrame        yuv;	 
    int i=100;  //encode 100 frames
	
    dout=fopen(dout_name,"wb");
    printf("Use encoder output name %s\n",dout_name);
    
    if(argc<2)
        exit(0);
    printf("Input File Name %s\n",argv[1]);
    din=fopen(argv[1],"rb");
	
    //set the default value
    video_setting.qmax = 31;
    video_setting.qmin = 1;
    video_setting.quant = 30;
    video_setting.bit_rate = 4096;
    video_setting.width = TEST_WIDTH;
    video_setting.height = TEST_HEIGHT;
    video_setting.framerate = 30;
    video_setting.frame_rate_base = 1;
    video_setting.gop_size = 60;

    out_virt_buffer = (unsigned char *) malloc(TEST_WIDTH*TEST_HEIGHT*3/2);	
      
    if(mpeg4_init(&video_setting)<0)
        return -1;
    
    mmap_addr = (int)mmap(0, (TEST_WIDTH * TEST_HEIGHT * 3*2)/2,PROT_READ|PROT_WRITE,MAP_SHARED,fmpeg4_enc_fd, 0);
    if(mmap_addr <= 0)
        return -1;
	
    fread((void *)mmap_addr,((TEST_WIDTH * TEST_HEIGHT*3)/2),1,din);
    fread((void *)mmap_addr+((TEST_WIDTH * TEST_HEIGHT*3)/2),(TEST_WIDTH * TEST_HEIGHT*3)/2,1,din);	
    printf("first YUV base 0x%x second YUV base 0x%x\n", mmap_addr, mmap_addr+((TEST_WIDTH * TEST_HEIGHT*3)/2) );
	
    time_start(&t1);

    tlength=0;
    fcount=1;
    while(1)  {
        int offset=0;		
        i=TEST_ROUND;
        while(i--)  {
            if((fcount%20)==0)   {
                //printf("==>Round %d\n",TEST_ROUND-i);
                nsecs = time_stop(&t1) / 1000;
                printf("[%06d] Spend %06lld ms, %ffps, br=%dKbps\n",fcount,nsecs,(float)((float)fcount)/((float)nsecs/(float)1000.0),((tlength/fcount)*8*30)/1000);
                fflush(stdout);
            }  
            fcount++;
            
            offset=fcount%2;
            yuv.data[0]=(unsigned char *)mmap_addr+(((TEST_WIDTH*TEST_HEIGHT*3)/2)*offset);
            yuv.data[1]=(unsigned char *)(mmap_addr+(((TEST_WIDTH*TEST_HEIGHT*3)/2)*offset)+(video_setting.width*video_setting.height));
            yuv.data[2]=(unsigned char *)(mmap_addr+(((TEST_WIDTH*TEST_HEIGHT*3)/2)*offset)+(video_setting.width*video_setting.height)+(video_setting.width*video_setting.height/4));
    
            length=mpeg4_encode(&video_setting, out_virt_buffer, &yuv);
	   //printf("out bs len %d total %d\n", length, tlength);	
	   //fflush(stdout);
            tlength+=length;
        }
    }
    
    nsecs = time_stop(&t1) / 1000;
    printf("[%06d] Spend %06lld ms, %ffps, br=%dKbps\n",fcount,nsecs,(float)((float)fcount)/((float)nsecs/(float)1000.0),((tlength/fcount)*8*30)/1000);
    fflush(stdout);
      
    fclose(din);
    fclose(dout);
    enc_close(&video_setting);
    free(out_virt_buffer);	
    return 0;
}
